This is an automated email from the ASF dual-hosted git repository.
larsh pushed a commit to branch 4.x-HBase-1.3
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/4.x-HBase-1.3 by this push:
new dd939f5 PHOENIX-5109 Allow local indexes to work for queries with
uncovered columns.
dd939f5 is described below
commit dd939f555752932ff911dc498c633cb23809e045
Author: Lars Hofhansl <[email protected]>
AuthorDate: Thu Jan 24 22:05:08 2019 -0800
PHOENIX-5109 Allow local indexes to work for queries with uncovered columns.
---
.../apache/phoenix/end2end/index/LocalIndexIT.java | 122 +++++++++++++++++++++
.../org/apache/phoenix/compile/WhereCompiler.java | 11 --
.../apache/phoenix/optimize/QueryOptimizer.java | 11 +-
3 files changed, 129 insertions(+), 15 deletions(-)
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
index d70a505..0c80db6 100644
---
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
@@ -102,6 +102,128 @@ public class LocalIndexIT extends BaseLocalIndexIT {
}
@Test
+ public void testUseUncoveredLocalIndex() throws Exception {
+ String tableName = schemaName + "." + generateUniqueName();
+ String indexName = "IDX_" + generateUniqueName();
+ TableName physicalTableName =
SchemaUtil.getPhysicalTableName(tableName.getBytes(), isNamespaceMapped);
+ String indexPhysicalTableName = physicalTableName.getNameAsString();
+
+ Connection conn = getConnection();
+ conn.setAutoCommit(true);
+ if (isNamespaceMapped) {
+ conn.createStatement().execute("CREATE SCHEMA IF NOT EXISTS " +
schemaName);
+ }
+
+ conn.createStatement().execute("CREATE TABLE " + tableName
+ + " (pk INTEGER PRIMARY KEY, v1 FLOAT, v2 FLOAT, V3 INTEGER,
V4 INTEGER)");
+ conn.createStatement().execute("CREATE LOCAL INDEX " + indexName + "
ON " + tableName + "(v2, v3, v4)");
+
+ // 1. COUNT(*) should still use the index - fewer bytes to scan
+ ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT
COUNT(*) FROM " + tableName);
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+ + indexPhysicalTableName + " [1]\n"
+ + " SERVER FILTER BY FIRST KEY ONLY\n"
+ + " SERVER AGGREGATE INTO SINGLE ROW",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 2. All column projected, no filtering by indexed column, not using
the index
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName);
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName,
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 3. if the index can avoid a sort operation, use it
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " ORDER BY v2");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+ + indexPhysicalTableName + " [1]\n"
+ + " SERVER FILTER BY FIRST KEY ONLY\n"
+ + "CLIENT MERGE SORT",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 4. but can't use the index if not ORDERing by a prefix of the index
key.
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " ORDER BY v3");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY FULL SCAN OVER "
+ + physicalTableName + "\n"
+ + " SERVER SORTED BY [V3]\n"
+ + "CLIENT MERGE SORT",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 5. If we pin the prefix of the index key we use the index avoiding
sorting on the postfix
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " WHERE v2 = 2 ORDER BY v3");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+ + indexPhysicalTableName + " [1,2]\n"
+ + " SERVER FILTER BY FIRST KEY ONLY\n"
+ + "CLIENT MERGE SORT",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 6. Filtering by a non-indexed column will not use the index
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " WHERE v1 = 3");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY FULL SCAN OVER "
+ + physicalTableName + "\n"
+ + " SERVER FILTER BY V1 = 3.0",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 7. Also don't use an index if not filtering on a prefix of the key
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " WHERE v3 = 1");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY FULL SCAN OVER "
+ + physicalTableName + "\n"
+ + " SERVER FILTER BY V3 = 1",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 8. Filtering along a prefix of the index key can use the index
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " WHERE v2 = 2");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+ + indexPhysicalTableName + " [1,2]\n"
+ + " SERVER FILTER BY FIRST KEY ONLY\n"
+ + "CLIENT MERGE SORT",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 9. Make sure a gap in the index columns still uses the index as
long as a prefix is specified
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " WHERE v2 = 2 AND v4 = 4");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+ + indexPhysicalTableName + " [1,2]\n"
+ + " SERVER FILTER BY FIRST KEY ONLY AND \"V4\"
= 4\n"
+ + "CLIENT MERGE SORT",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 10. Use index even when also filtering on non-indexed column
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " WHERE v2 = 2 AND v1 = 3");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+ + indexPhysicalTableName + " [1,2]\n"
+ + " SERVER FILTER BY FIRST KEY ONLY AND \"V1\"
= 3.0\n"
+ + "CLIENT MERGE SORT",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+
+ // 11. Another case of not using a prefix of the index key
+ rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " +
tableName + " WHERE v1 = 3 AND v3 = 1 AND v4 = 1");
+ assertEquals(
+ "CLIENT PARALLEL 1-WAY FULL SCAN OVER "
+ + physicalTableName + "\n"
+ + " SERVER FILTER BY (V1 = 3.0 AND V3 = 1 AND
V4 = 1)",
+ QueryUtil.getExplainPlan(rs));
+ rs.close();
+ }
+
+ @Test
public void testLocalIndexRoundTrip() throws Exception {
String tableName = schemaName + "." + generateUniqueName();
String indexName = "IDX_" + generateUniqueName();
diff --git
a/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereCompiler.java
b/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereCompiler.java
index 832b1f0..2c7926e 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereCompiler.java
@@ -190,17 +190,6 @@ public class WhereCompiler {
protected ColumnRef resolveColumn(ColumnParseNode node) throws
SQLException {
ColumnRef ref = super.resolveColumn(node);
PTable table = ref.getTable();
- // if current table in the context is local index and table in
column reference is global means
- // the column is not present in the local index. If where
condition contains the column
- // not present in the index then we need to go through main table
for each row in index and get the
- // missing column which is like full scan of index table and data
table. Which is
- // inefficient. Then we can skip this plan.
- if (context.getCurrentTable().getTable().getIndexType() ==
IndexType.LOCAL
- && (table.getIndexType() == null || table.getIndexType()
== IndexType.GLOBAL)) {
- String schemaNameStr =
table.getSchemaName()==null?null:table.getSchemaName().getString();
- String tableNameStr =
table.getTableName()==null?null:table.getTableName().getString();
- throw new ColumnNotFoundException(schemaNameStr, tableNameStr,
null, ref.getColumn().getName().getString());
- }
// Track if we need to compare KeyValue during filter evaluation
// using column family. If the column qualifier is enough, we
// just use that.
diff --git
a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
index 4595a36..20c84cc 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
@@ -59,6 +59,7 @@ import org.apache.phoenix.parse.SelectStatement;
import org.apache.phoenix.parse.TableName;
import org.apache.phoenix.parse.TableNode;
import org.apache.phoenix.parse.TableNodeVisitor;
+import org.apache.phoenix.query.KeyRange;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.query.QueryServicesOptions;
import org.apache.phoenix.schema.ColumnNotFoundException;
@@ -326,13 +327,15 @@ public class QueryOptimizer {
boolean optimizedOrderBy =
plan.getOrderBy().getOrderByExpressions().isEmpty() &&
!dataPlan.getOrderBy().getOrderByExpressions().isEmpty();
- // If query doesn't have where clause and some of columns to
project are missing
- // in the index then we need to get missing columns from main
table for each row in
- // local index. It's like full scan of both local index and
data table which is inefficient.
+ // If query doesn't have where clause, or the planner didn't
add any (bound) scan ranges, and some of
+ // columns to project/filter are missing in the index then we
need to get missing columns from main table
+ // for each row in local index. It's like full scan of both
local index and data table which is inefficient.
// Then we don't use the index. If all the columns to project
are present in the index
// then we can use the index even the query doesn't have where
clause.
// We'll use the index anyway if it allowed us to optimize an
ORDER BY clause away.
- if (index.getIndexType() == IndexType.LOCAL &&
indexSelect.getWhere() == null
+ if (index.getIndexType() == IndexType.LOCAL
+ && (indexSelect.getWhere() == null
+ ||
plan.getContext().getScanRanges().getBoundRanges().size() == 1)
&& !plan.getContext().getDataColumns().isEmpty() &&
!optimizedOrderBy) {
return null;
}