You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by st...@apache.org on 2023/07/20 16:08:29 UTC

[phoenix] branch master updated: PHOENIX-6961 Using a covered field in hinted non-covered indexed query fails

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5f2eb21610 PHOENIX-6961 Using a covered field in hinted non-covered indexed query fails
5f2eb21610 is described below

commit 5f2eb21610b54c27395266f29b5ebea98ff7af93
Author: Istvan Toth <st...@apache.org>
AuthorDate: Tue Jun 20 07:53:23 2023 +0200

    PHOENIX-6961 Using a covered field in hinted non-covered indexed query fails
---
 .../apache/phoenix/end2end/index/BaseIndexIT.java  | 109 +++++++++++++++++++++
 .../end2end/index/GlobalMutableNonTxIndexIT.java   |   1 -
 .../apache/phoenix/compile/ExpressionCompiler.java |   4 +-
 .../org/apache/phoenix/compile/JoinCompiler.java   |   6 +-
 .../apache/phoenix/compile/ProjectionCompiler.java |  99 ++++++++++++-------
 .../phoenix/compile/TupleProjectionCompiler.java   |  12 +--
 ...mnRef.java => IndexUncoveredDataColumnRef.java} |  10 +-
 .../java/org/apache/phoenix/schema/PTableImpl.java |  29 ++++--
 .../java/org/apache/phoenix/util/ScanUtil.java     |   4 +-
 .../apache/phoenix/compile/QueryCompilerTest.java  |  17 ++++
 .../java/org/apache/phoenix/query/BaseTest.java    |  35 +++----
 11 files changed, 246 insertions(+), 80 deletions(-)

diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java
index 8bf84296f3..e4d759e218 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java
@@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import java.io.IOException;
 import java.math.BigDecimal;
@@ -37,6 +38,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
@@ -81,6 +83,7 @@ import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.EnvironmentEdgeManager;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.SchemaUtil;
 import org.apache.phoenix.util.TestUtil;
 import org.apache.phoenix.util.TransactionUtil;
@@ -1521,4 +1524,110 @@ public abstract class BaseIndexIT extends ParallelStatsDisabledIT {
             CreateTableIT.verifyLastDDLTimestamp(fullIndexName, activeIndexLastDDLTimestamp + 1, conn);
         }
     }
+
+    @Test
+    public void testSelectUncoveredWithCoveredFieldSimple() throws Exception {
+        assumeFalse(localIndex);
+        assumeFalse(uncovered);
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        String tableName = "TBL_" + generateUniqueName();
+        String indexName = "IND_" + generateUniqueName();
+        String fullTableName = SchemaUtil.getTableName(TestUtil.DEFAULT_SCHEMA_NAME, tableName);
+        try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+            conn.setAutoCommit(false);
+            String ddl =
+                    "CREATE TABLE " + fullTableName
+                            + " (v1 integer, k integer primary key, v2 integer, v3 integer, v4 integer) "
+                            + tableDDLOptions;
+            String upsert = "UPSERT INTO " + fullTableName + " values (1, 2, 3, 4, 5)";
+            Statement stmt = conn.createStatement();
+            stmt.execute(ddl);
+            stmt.execute(upsert);
+            conn.commit();
+            ddl =
+                    "CREATE " + (uncovered ? "UNCOVERED" : "") + " INDEX " + indexName + " ON "
+                            + fullTableName + " (v2) include(v3)";
+            conn.createStatement().execute(ddl);
+            ResultSet rs =
+                    conn.createStatement().executeQuery("SELECT /*+ INDEX(" + fullTableName + " "
+                            + indexName + ")*/ * FROM " + fullTableName + " where v2=3 and v3=4");
+            // We're only testing the projector
+            assertTrue(rs.next());
+            assertEquals("1", rs.getString("v1"));
+            assertEquals("2", rs.getString("k"));
+            assertEquals("3", rs.getString("v2"));
+            assertEquals("4", rs.getString("v3"));
+            assertEquals("5", rs.getString("v4"));
+            assertFalse(rs.next());
+        }
+    }
+
+    @Test
+    public void testSelectUncoveredWithCoveredField() throws Exception {
+        assumeFalse(localIndex);
+        assumeFalse(uncovered);
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        String tableName = "TBL_" + generateUniqueName();
+        String indexName = "IND_" + generateUniqueName();
+        String fullTableName = SchemaUtil.getTableName(TestUtil.DEFAULT_SCHEMA_NAME, tableName);
+        String fullIndexName = SchemaUtil.getTableName(TestUtil.DEFAULT_SCHEMA_NAME, indexName);
+
+
+        try (Connection conn = DriverManager.getConnection(getUrl(), props);
+                Statement stmt = conn.createStatement()) {
+            conn.setAutoCommit(false);
+            String ddl =
+                    "CREATE TABLE " + fullTableName + TestUtil.TEST_TABLE_SCHEMA + tableDDLOptions;
+            stmt.execute(ddl);
+            BaseTest.populateTestTable(fullTableName);
+            conn.commit();
+            ddl =
+                    "CREATE " + (uncovered ? "UNCOVERED" : "") + " INDEX " + indexName + " ON "
+                            + fullTableName + " ( int_col1 ASC)"
+                            + " INCLUDE (long_col1, long_col2)"
+            ;
+            stmt.execute(ddl);
+
+            String query;
+            ResultSet rs;
+
+            for (String columns : Arrays.asList(new String[] { "*",
+                    "varchar_pk,char_pk,int_pk,long_pk,decimal_pk,date_pk,a.varchar_col1,a.char_col1,a.int_col1,a.long_col1,a.decimal_col1,a.date1,b.varchar_col2,b.char_col2,b.int_col2,b.long_col2,b.decimal_col2,b.date2",
+                    "varchar_pk,char_pk,int_pk,long_pk,decimal_pk,date_pk,varchar_col1,char_col1,int_col1,long_col1,decimal_col1,date1,varchar_col2,char_col2,int_col2,long_col2,decimal_col2,date2", })) {
+
+                query =
+                        "SELECT /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " + columns
+                                + " from " + fullTableName + " where int_col1=2 and long_col1=2";
+                rs = stmt.executeQuery("Explain " + query);
+                String explainPlan = QueryUtil.getExplainPlan(rs);
+                assertEquals("bad plan with columns:" + columns,
+                    "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + " [2]\n"
+                            + "    SERVER MERGE [A.VARCHAR_COL1, A.CHAR_COL1, A.DECIMAL_COL1, A.DATE1, B.VARCHAR_COL2, B.CHAR_COL2, B.INT_COL2, B.DECIMAL_COL2, B.DATE2]\n"
+                            + "    SERVER FILTER BY A.\"LONG_COL1\" = 2",
+                    explainPlan);
+                rs = stmt.executeQuery(query);
+                assertTrue(rs.next());
+                // Test the projector thoroughly
+                assertEquals("varchar1", rs.getString("varchar_pk"));
+                assertEquals("char1", rs.getString("char_pk"));
+                assertEquals("1", rs.getString("int_pk"));
+                assertEquals("1", rs.getString("long_pk"));
+                assertEquals("1", rs.getString("decimal_pk"));
+                assertEquals("2015-01-01 00:00:00.000", rs.getString("date_pk"));
+                assertEquals("varchar_a", rs.getString("varchar_col1"));
+                assertEquals("chara", rs.getString("char_col1"));
+                assertEquals("2", rs.getString("int_col1"));
+                assertEquals("2", rs.getString("long_col1"));
+                assertEquals("2", rs.getString("decimal_col1"));
+                assertEquals("2015-01-01 00:00:00.000", rs.getString("date1"));
+                assertEquals("varchar_b", rs.getString("varchar_col2"));
+                assertEquals("charb", rs.getString("char_col2"));
+                assertEquals("3", rs.getString("int_col2"));
+                assertEquals("3", rs.getString("long_col2"));
+                assertEquals("3", rs.getString("decimal_col2"));
+                assertEquals("2015-01-01 00:00:00.000", rs.getString("date2"));
+                assertFalse(rs.next());
+            }
+        }
+    }
 }
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalMutableNonTxIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalMutableNonTxIndexIT.java
index 28da77564e..fbea096280 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalMutableNonTxIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalMutableNonTxIndexIT.java
@@ -21,7 +21,6 @@ import java.util.Arrays;
 import java.util.Collection;
 
 import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
-import org.apache.phoenix.hbase.index.IndexRegionObserver;
 import org.junit.experimental.categories.Category;
 import org.junit.runners.Parameterized.Parameters;
 
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
index 09a6adfbe4..1cd167ee52 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
@@ -112,7 +112,7 @@ import org.apache.phoenix.schema.ColumnFamilyNotFoundException;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.ColumnRef;
 import org.apache.phoenix.schema.DelegateDatum;
-import org.apache.phoenix.schema.IndexDataColumnRef;
+import org.apache.phoenix.schema.IndexUncoveredDataColumnRef;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PDatum;
 import org.apache.phoenix.schema.PTable;
@@ -373,7 +373,7 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
             if (IndexUtil.shouldIndexBeUsedForUncoveredQuery(context.getCurrentTable())) {
                 try {
                     context.setUncoveredIndex(true);
-                    return new IndexDataColumnRef(context, context.getCurrentTable(),
+                    return new IndexUncoveredDataColumnRef(context, context.getCurrentTable(),
                             node.getName());
                 } catch (ColumnFamilyNotFoundException c) {
                     throw e;
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
index 4093436b3b..04dd4ffaa5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
@@ -75,7 +75,7 @@ import org.apache.phoenix.parse.TableNodeVisitor;
 import org.apache.phoenix.parse.TableWildcardParseNode;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.ColumnRef;
-import org.apache.phoenix.schema.IndexDataColumnRef;
+import org.apache.phoenix.schema.IndexUncoveredDataColumnRef;
 import org.apache.phoenix.schema.MetaDataEntityNotFoundException;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PNameFactory;
@@ -1127,7 +1127,7 @@ public class JoinCompiler {
                     if (columnRef.getTableRef().equals(tableRef)
                             && (!retainPKColumns || !SchemaUtil.isPKColumn(columnRef.getColumn()))) {
                         if (columnRef instanceof LocalIndexColumnRef) {
-                            sourceColumns.add(new IndexDataColumnRef(context, tableRef,
+                            sourceColumns.add(new IndexUncoveredDataColumnRef(context, tableRef,
                                     IndexUtil.getIndexColumnName(columnRef.getColumn())));
                         } else {
                             sourceColumns.add(columnRef);
@@ -1393,7 +1393,7 @@ public class JoinCompiler {
             try {
                 columnRef = resolver.resolveColumn(node.getSchemaName(), node.getTableName(), node.getName());
             } catch (ColumnNotFoundException e) {
-                // This could be an IndexDataColumnRef. If so, the table name must have
+                // This could be an IndexUncoveredDataColumnRef. If so, the table name must have
                 // been appended by the IndexStatementRewriter, and we can convert it into.
                 TableRef tableRef = resolver.resolveTable(node.getSchemaName(), node.getTableName());
                 if (IndexUtil.shouldIndexBeUsedForUncoveredQuery(tableRef)) {
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
index 80c2156bdf..1fc74297b2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
@@ -69,7 +69,7 @@ import org.apache.phoenix.schema.ArgumentTypeMismatchException;
 import org.apache.phoenix.schema.ColumnFamilyNotFoundException;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.ColumnRef;
-import org.apache.phoenix.schema.IndexDataColumnRef;
+import org.apache.phoenix.schema.IndexUncoveredDataColumnRef;
 import org.apache.phoenix.schema.KeyValueSchema;
 import org.apache.phoenix.schema.KeyValueSchema.KeyValueSchemaBuilder;
 import org.apache.phoenix.schema.PColumn;
@@ -78,7 +78,6 @@ import org.apache.phoenix.schema.PDatum;
 import org.apache.phoenix.schema.PName;
 import org.apache.phoenix.schema.PTable;
 import org.apache.phoenix.schema.PTable.ImmutableStorageScheme;
-import org.apache.phoenix.schema.PTable.IndexType;
 import org.apache.phoenix.schema.PTableKey;
 import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.ProjectedColumn;
@@ -191,17 +190,17 @@ public class ProjectionCompiler {
         int projectedOffset = projectedExpressions.size();
         PhoenixConnection conn = context.getConnection();
         PName tenantId = conn.getTenantId();
-        String tableName = index.getParentName().getString();
+        String dataTableName = index.getParentName().getString();
         PTable dataTable = null;
         try {
-        	dataTable = conn.getTable(new PTableKey(tenantId, tableName));
+            dataTable = conn.getTable(new PTableKey(tenantId, dataTableName));
         } catch (TableNotFoundException e) {
             if (tenantId != null) {
                 // Check with null tenantId
-            	dataTable = conn.getTable(new PTableKey(null, tableName));
+                dataTable = conn.getTable(new PTableKey(null, dataTableName));
             }
             else {
-            	throw e;
+                throw e;
             }
         }
         int tableOffset = dataTable.getBucketNum() == null ? 0 : 1;
@@ -215,6 +214,13 @@ public class ProjectionCompiler {
                 throw new ColumnNotFoundException(schemaNameStr, tableNameStr,null, WildcardParseNode.INSTANCE.toString());
             }
         }
+        // At this point, the index table is either fully covered, or we are projecting uncovered
+        // columns
+        // The easy thing would be to just call projectAllTableColumns on the projected table,
+        // but its columns are not in the same order as the data column, so we have to map them to
+        // the data column order
+        TableRef projectedTableRef =
+                new TableRef(resolver.getTables().get(0), tableRef.getTableAlias());
         for (int i = tableOffset, j = tableOffset; i < dataTable.getColumns().size(); i++) {
             PColumn column = dataTable.getColumns().get(i);
             // Skip tenant ID column (which may not be the first column, but is the first PK column)
@@ -222,29 +228,34 @@ public class ProjectionCompiler {
                 tableOffset++;
                 continue;
             }
-            PColumn tableColumn = dataTable.getColumns().get(i);
-            String indexColName = IndexUtil.getIndexColumnName(tableColumn);
+            PColumn dataTableColumn = dataTable.getColumns().get(i);
+            String indexColName = IndexUtil.getIndexColumnName(dataTableColumn);
             PColumn indexColumn = null;
             ColumnRef ref = null;
             try {
                 indexColumn = index.getColumnForColumnName(indexColName);
-                ref = new ColumnRef(tableRef, indexColumn.getPosition());
+                //TODO could should we do this more efficiently than catching the expcetion ?
             } catch (ColumnNotFoundException e) {
                 if (IndexUtil.shouldIndexBeUsedForUncoveredQuery(tableRef)) {
-                    try {
-                        context.setUncoveredIndex(true);
-                        ref = new IndexDataColumnRef(context, tableRef, indexColName);
-                        indexColumn = ref.getColumn();
-                    } catch (ColumnFamilyNotFoundException c) {
-                        throw e;
-                    }
+                    //Projected columns have the same name as in the data table
+                    String familyName =
+                            dataTableColumn.getFamilyName() == null ? null
+                                    : dataTableColumn.getFamilyName().getString();
+                    ref =
+                            resolver.resolveColumn(familyName,
+                                tableRef.getTableAlias() == null
+                                        ? tableRef.getTable().getName().getString()
+                                        : tableRef.getTableAlias(),
+                                indexColName);
+                    indexColumn = ref.getColumn();
                 } else {
                     throw e;
                 }
             }
-            String colName = tableColumn.getName().getString();
+            ref = new ColumnRef(projectedTableRef, indexColumn.getPosition());
+            String colName = dataTableColumn.getName().getString();
             String tableAlias = tableRef.getTableAlias();
-            if (resolveColumn && !(ref instanceof IndexDataColumnRef)) {
+            if (resolveColumn) {
                 try {
                     if (tableAlias != null) {
                         ref = resolver.resolveColumn(null, tableAlias, indexColName);
@@ -289,11 +300,15 @@ public class ProjectionCompiler {
     }
 
     private static void projectIndexColumnFamily(StatementContext context, String cfName, TableRef tableRef, boolean resolveColumn, List<Expression> projectedExpressions, List<ExpressionProjector> projectedColumns) throws SQLException {
+        ColumnResolver resolver = context.getResolver();
         PTable index = tableRef.getTable();
         PhoenixConnection conn = context.getConnection();
-        String tableName = index.getParentName().getString();
-        PTable table = conn.getTable(new PTableKey(conn.getTenantId(), tableName));
-        PColumnFamily pfamily = table.getColumnFamily(cfName);
+        String dataTableName = index.getParentName().getString();
+        PTable dataTable = conn.getTable(new PTableKey(conn.getTenantId(), dataTableName));
+        PColumnFamily pfamily = dataTable.getColumnFamily(cfName);
+        TableRef projectedTableRef =
+                new TableRef(resolver.getTables().get(0), tableRef.getTableAlias());
+        PTable projectedIndex = projectedTableRef.getTable();
         for (PColumn column : pfamily.getColumns()) {
             String indexColName = IndexUtil.getIndexColumnName(column);
             PColumn indexColumn = null;
@@ -301,20 +316,24 @@ public class ProjectionCompiler {
             String indexColumnFamily = null;
             try {
                 indexColumn = index.getColumnForColumnName(indexColName);
-                ref = new ColumnRef(tableRef, indexColumn.getPosition());
-                indexColumnFamily = indexColumn.getFamilyName() == null ? null : indexColumn.getFamilyName().getString();
+                ref = new ColumnRef(projectedTableRef, indexColumn.getPosition());
+                indexColumnFamily =
+                        indexColumn.getFamilyName() == null ? null
+                                : indexColumn.getFamilyName().getString();
             } catch (ColumnNotFoundException e) {
                 if (IndexUtil.shouldIndexBeUsedForUncoveredQuery(tableRef)) {
                     try {
-                        context.setUncoveredIndex(true);
-                        ref = new IndexDataColumnRef(context, tableRef, indexColName);
-                        indexColumn = ref.getColumn();
-                        indexColumnFamily =
-                                indexColumn.getFamilyName() == null ? null
-                                        : (index.getIndexType() == IndexType.LOCAL ? IndexUtil
-                                                .getLocalIndexColumnFamily(indexColumn
-                                                        .getFamilyName().getString()) : indexColumn
-                                                .getFamilyName().getString());
+                        //Projected columns have the same name as in the data table
+                        String colName = column.getName().getString();
+                        String familyName =
+                                column.getFamilyName() == null ? null
+                                        : column.getFamilyName().getString();
+                        resolver.resolveColumn(familyName,
+                            tableRef.getTableAlias() == null
+                                    ? tableRef.getTable().getName().getString()
+                                    : tableRef.getTableAlias(),
+                            indexColName);
+                        indexColumn = projectedIndex.getColumnForColumnName(colName);
                     } catch (ColumnFamilyNotFoundException c) {
                         throw e;
                     }
@@ -323,14 +342,18 @@ public class ProjectionCompiler {
                 }
             }
             if (resolveColumn) {
-                ref = context.getResolver().resolveColumn(index.getTableName().getString(), indexColumnFamily, indexColName);
+                ref =
+                        resolver.resolveColumn(index.getTableName().getString(), indexColumnFamily,
+                            indexColName);
             }
             Expression expression = ref.newColumnExpression();
             projectedExpressions.add(expression);
             String colName = column.getName().toString();
             boolean isCaseSensitive = !SchemaUtil.normalizeIdentifier(colName).equals(colName);
-            projectedColumns.add(new ExpressionProjector(colName, 
-                    tableRef.getTableAlias() == null ? table.getName().getString() : tableRef.getTableAlias(), expression, isCaseSensitive));
+            projectedColumns.add(new ExpressionProjector(colName,
+                    tableRef.getTableAlias() == null ? dataTable.getName().getString()
+                            : tableRef.getTableAlias(),
+                    expression, isCaseSensitive));
         }
     }
     
@@ -397,7 +420,7 @@ public class ProjectionCompiler {
                 }
                 isWildcard = true;
                 if (tableRef.getTable().getType() == PTableType.INDEX && ((WildcardParseNode)node).isRewrite()) {
-                	projectAllIndexColumns(context, tableRef, resolveColumn, projectedExpressions, projectedColumns, targetColumns);
+                    projectAllIndexColumns(context, tableRef, resolveColumn, projectedExpressions, projectedColumns, targetColumns);
                 } else {
                     projectAllTableColumns(context, tableRef, resolveColumn, projectedExpressions, projectedColumns, targetColumns);
                 }
@@ -411,7 +434,7 @@ public class ProjectionCompiler {
                     projectAllIndexColumns(context, tRef, true, projectedExpressions, projectedColumns, targetColumns);
                 } else {
                     projectAllTableColumns(context, tRef, true, projectedExpressions, projectedColumns, targetColumns);
-                }                
+                }
             } else if (node instanceof  FamilyWildcardParseNode) {
                 if (tableRef == TableRef.EMPTY_TABLE_REF) {
                     throw new SQLExceptionInfo.Builder(SQLExceptionCode.NO_TABLE_SPECIFIED_FOR_WILDCARD_SELECT).build().buildException();
@@ -712,7 +735,7 @@ public class ProjectionCompiler {
                              PColumn col = expression.getColumn();
                              // hack'ish... For covered columns with local indexes we defer to the server.
                              if (col instanceof ProjectedColumn && ((ProjectedColumn) col)
-                                     .getSourceColumnRef() instanceof IndexDataColumnRef) {
+                                     .getSourceColumnRef() instanceof IndexUncoveredDataColumnRef) {
                                  return null;
                              }
                              PTable table = context.getCurrentTable().getTable();
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/TupleProjectionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/TupleProjectionCompiler.java
index 0c4c514122..99392f6d29 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/TupleProjectionCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/TupleProjectionCompiler.java
@@ -41,7 +41,7 @@ import org.apache.phoenix.parse.WildcardParseNode;
 import org.apache.phoenix.schema.ColumnFamilyNotFoundException;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.ColumnRef;
-import org.apache.phoenix.schema.IndexDataColumnRef;
+import org.apache.phoenix.schema.IndexUncoveredDataColumnRef;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PName;
 import org.apache.phoenix.schema.PNameFactory;
@@ -159,9 +159,9 @@ public class TupleProjectionCompiler {
             	EncodedColumnsUtil.setColumns(column, table, context.getScan());
             }
         }
-        // add IndexDataColumnRef
+        // add IndexUncoveredDataColumnRef
         position = projectedColumns.size() + (hasSaltingColumn ? 1 : 0);
-        for (IndexDataColumnRef sourceColumnRef : visitor.indexColumnRefSet) {
+        for (IndexUncoveredDataColumnRef sourceColumnRef : visitor.indexColumnRefSet) {
             PColumn column = new ProjectedColumn(sourceColumnRef.getColumn().getName(), 
                     sourceColumnRef.getColumn().getFamilyName(), position++, 
                     sourceColumnRef.getColumn().isNullable(), sourceColumnRef, sourceColumnRef.getColumn().getColumnQualifierBytes());
@@ -239,12 +239,12 @@ public class TupleProjectionCompiler {
     private static class ColumnRefVisitor extends StatelessTraverseAllParseNodeVisitor {
         private final StatementContext context;
         private final LinkedHashSet<ColumnRef> nonPkColumnRefSet;
-        private final LinkedHashSet<IndexDataColumnRef> indexColumnRefSet;
+        private final LinkedHashSet<IndexUncoveredDataColumnRef> indexColumnRefSet;
         
         private ColumnRefVisitor(StatementContext context) {
             this.context = context;
             this.nonPkColumnRefSet = new LinkedHashSet<ColumnRef>();
-            this.indexColumnRefSet = new LinkedHashSet<IndexDataColumnRef>();
+            this.indexColumnRefSet = new LinkedHashSet<IndexUncoveredDataColumnRef>();
         }
 
         @Override
@@ -259,7 +259,7 @@ public class TupleProjectionCompiler {
                 if (IndexUtil.shouldIndexBeUsedForUncoveredQuery(context.getCurrentTable())) {
                     try {
                         context.setUncoveredIndex(true);
-                        indexColumnRefSet.add(new IndexDataColumnRef(context,
+                        indexColumnRefSet.add(new IndexUncoveredDataColumnRef(context,
                                 context.getCurrentTable(), node.getName()));
                     } catch (ColumnFamilyNotFoundException c) {
                         throw e;
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/IndexDataColumnRef.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/IndexUncoveredDataColumnRef.java
similarity index 89%
rename from phoenix-core/src/main/java/org/apache/phoenix/schema/IndexDataColumnRef.java
rename to phoenix-core/src/main/java/org/apache/phoenix/schema/IndexUncoveredDataColumnRef.java
index 6b8c0e2952..2bffdbb5c3 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/IndexDataColumnRef.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/IndexUncoveredDataColumnRef.java
@@ -33,13 +33,13 @@ import org.apache.phoenix.util.IndexUtil;
  * use index in the query plan and fetch the missing columns from the data table rows on the
  * server side. This class is used to keep track of such data columns.
  */
-public class IndexDataColumnRef extends ColumnRef {
+public class IndexUncoveredDataColumnRef extends ColumnRef {
     final private int position;
     // Despite the final keyword, columns IS mutable, and must not be used for equality/hashCode
     final private Set<PColumn> columns;
     private static final ParseNodeFactory FACTORY = new ParseNodeFactory();
 
-    public IndexDataColumnRef(StatementContext context, TableRef tRef, String indexColumnName)
+    public IndexUncoveredDataColumnRef(StatementContext context, TableRef tRef, String indexColumnName)
             throws MetaDataEntityNotFoundException, SQLException {
         super(FromCompiler.getResolver(
             FACTORY.namedTable(
@@ -54,7 +54,7 @@ public class IndexDataColumnRef extends ColumnRef {
         columns = context.getDataColumns();
     }
 
-    protected IndexDataColumnRef(IndexDataColumnRef indexDataColumnRef, long timestamp) {
+    protected IndexUncoveredDataColumnRef(IndexUncoveredDataColumnRef indexDataColumnRef, long timestamp) {
         super(indexDataColumnRef, timestamp);
         this.position = indexDataColumnRef.position;
         this.columns = indexDataColumnRef.columns;
@@ -62,7 +62,7 @@ public class IndexDataColumnRef extends ColumnRef {
 
     @Override
     public ColumnRef cloneAtTimestamp(long timestamp) {
-        return new IndexDataColumnRef(this, timestamp);
+        return new IndexUncoveredDataColumnRef(this, timestamp);
     }
 
     @Override
@@ -84,7 +84,7 @@ public class IndexDataColumnRef extends ColumnRef {
         if (!super.equals(o)) {
             return false;
         }
-        IndexDataColumnRef that = (IndexDataColumnRef) o;
+        IndexUncoveredDataColumnRef that = (IndexUncoveredDataColumnRef) o;
         return position == that.position;
     }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java
index 1d55ea8b21..fb23a381c3 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java
@@ -744,14 +744,25 @@ public class PTableImpl implements PTable {
                 }
             });
 
+            // With the new uncovered index code, we pass the data table columns to the index
+            // PTable. This wreaks havoc with the code used disambiguate column qualifiers.
+            // localColumns only holds the actual columns of the table, and not external references
+            List<PColumn> localColumns = new ArrayList<>(this.columns.size());
+
+            //TODO should we just pass the global indexref columns separately instead ?
+            for (PColumn column : sortedColumns) {
+                if (!(column instanceof ProjectedColumn && ((ProjectedColumn) column)
+                        .getSourceColumnRef() instanceof IndexUncoveredDataColumnRef)) {
+                    localColumns.add(column);
+                }
+            }
+
             int position = 0;
             if (this.bucketNum != null) {
                 position = 1;
             }
             ListMultimap<String, PColumn> populateColumnsByName =
                     ArrayListMultimap.create(this.columns.size(), 1);
-            Map<KVColumnFamilyQualifier, PColumn> populateKvColumnsByQualifiers =
-                    Maps.newHashMapWithExpectedSize(this.columns.size());
             for (PColumn column : sortedColumns) {
                 allColumns[position] = column;
                 position++;
@@ -772,6 +783,10 @@ public class PTableImpl implements PTable {
                         }
                     }
                 }
+            }
+            Map<KVColumnFamilyQualifier, PColumn> populateKvColumnsByQualifiers =
+                    Maps.newHashMapWithExpectedSize(localColumns.size());
+            for (PColumn column : localColumns) {
                 byte[] cq = column.getColumnQualifierBytes();
                 String cf = column.getFamilyName() != null ?
                         column.getFamilyName().getString() : null;
@@ -779,7 +794,7 @@ public class PTableImpl implements PTable {
                     KVColumnFamilyQualifier info = new KVColumnFamilyQualifier(cf, cq);
                     if (populateKvColumnsByQualifiers.get(info) != null) {
                         throw new ColumnAlreadyExistsException(this.schemaName.getString(),
-                                fullName.getString(), columnName);
+                                fullName.getString(), column.getName().getString());
                     }
                     populateKvColumnsByQualifiers.put(info, column);
                 }
@@ -815,11 +830,13 @@ public class PTableImpl implements PTable {
                     if (column.isRowTimestamp()) {
                         rowTimestampCol = column;
                     }
-                }
-                if (familyName == null) {
                     estimatedSize += column.getEstimatedSize(); // PK columns
                     builder.addField(column, column.isNullable(), column.getSortOrder());
-                } else {
+                }
+            }
+            for (PColumn column : localColumns) {
+                PName familyName = column.getFamilyName();
+                if (familyName != null) {
                     List<PColumn> columnsInFamily = familyMap.get(familyName);
                     if (columnsInFamily == null) {
                         columnsInFamily = Lists.newArrayListWithExpectedSize(maxExpectedSize);
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java
index 3928142f95..c02acd9379 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java
@@ -1194,10 +1194,10 @@ public class ScanUtil {
             scan.setAttribute(BaseScannerRegionObserver.EMPTY_COLUMN_QUALIFIER_NAME, emptyCQ);
             scan.setAttribute(BaseScannerRegionObserver.READ_REPAIR_TRANSFORMING_TABLE, TRUE_BYTES);
         } else {
-            if (table.isTransactional() || table.getType() != PTableType.INDEX) {
+            if (table.getType() != PTableType.INDEX || !IndexUtil.isGlobalIndex(indexTable)) {
                 return;
             }
-            if (!IndexUtil.isGlobalIndex(indexTable)) {
+            if (table.isTransactional() && table.getIndexType() == IndexType.UNCOVERED_GLOBAL) {
                 return;
             }
             PTable dataTable = ScanUtil.getDataTable(indexTable, phoenixConnection);
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 df0ae054cf..084a87ee4f 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
@@ -7082,4 +7082,21 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
                 explainPlan);
         }
     }
+
+    @Test
+    public void testUncoveredPhoenix6961() throws Exception {
+        try (Connection conn = DriverManager.getConnection(getUrl());
+                Statement stmt = conn.createStatement();) {
+            stmt.execute(
+                "create table d (k integer primary key, v1 integer, v2 integer, v3 integer, v4 integer)");
+            stmt.execute("create index i on d(v2) include (v3)");
+            String query = "select /*+ index(d i) */ * from d where v2=1 and v3=1";
+            ResultSet rs = stmt.executeQuery("EXPLAIN " + query);
+            String explainPlan = QueryUtil.getExplainPlan(rs);
+            assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER I [1]\n"
+                    + "    SERVER MERGE [0.V1, 0.V4]\n"
+                    + "    SERVER FILTER BY \"V3\" = 1",
+                    explainPlan);
+        }
+    }
 }
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
index 30f73f77c8..fd8f0903b0 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
@@ -1766,25 +1766,26 @@ public abstract class BaseTest {
         String upsert = "UPSERT INTO " + fullTableName
                 + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
         PreparedStatement stmt = conn.prepareStatement(upsert);
+        //varchar_pk
         stmt.setString(1, firstRowInBatch ? "firstRowInBatch_" : "" + "varchar"+index);
-        stmt.setString(2, "char"+index);
-        stmt.setInt(3, index);
-        stmt.setLong(4, index);
-        stmt.setBigDecimal(5, new BigDecimal(index));
+        stmt.setString(2, "char"+index); // char_pk
+        stmt.setInt(3, index); // int_pk
+        stmt.setLong(4, index); // long_pk
+        stmt.setBigDecimal(5, new BigDecimal(index)); // decimal_pk
         Date date = DateUtil.parseDate("2015-01-01 00:00:00");
-        stmt.setDate(6, date);
-        stmt.setString(7, "varchar_a");
-        stmt.setString(8, "chara");
-        stmt.setInt(9, index+1);
-        stmt.setLong(10, index+1);
-        stmt.setBigDecimal(11, new BigDecimal(index+1));
-        stmt.setDate(12, date);
-        stmt.setString(13, "varchar_b");
-        stmt.setString(14, "charb");
-        stmt.setInt(15, index+2);
-        stmt.setLong(16, index+2);
-        stmt.setBigDecimal(17, new BigDecimal(index+2));
-        stmt.setDate(18, date);
+        stmt.setDate(6, date); // date_pk
+        stmt.setString(7, "varchar_a"); // a.varchar_col1
+        stmt.setString(8, "chara"); // a.char_col1
+        stmt.setInt(9, index+1); // a.int_col1
+        stmt.setLong(10, index+1); // a.long_col1
+        stmt.setBigDecimal(11, new BigDecimal(index+1)); // a.decimal_col1
+        stmt.setDate(12, date); // a.date1
+        stmt.setString(13, "varchar_b"); // b.varchar_col2
+        stmt.setString(14, "charb"); // b.char_col2
+        stmt.setInt(15, index+2); // b.int_col2
+        stmt.setLong(16, index+2); // b.long_col2
+        stmt.setBigDecimal(17, new BigDecimal(index+2)); // b.decimal_col2
+        stmt.setDate(18, date); // b.date2
         stmt.executeUpdate();
     }