diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java index 5f5245ac601..b0061fae713 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java @@ -254,7 +254,44 @@ private void testOptimization(String dataTableName, String dataTableFullName, St assertEquals(4, rs.getInt("k3")); assertEquals("z", rs.getString("V1")); assertFalse(rs.next()); - + + //Same as above, but with SKIP-SCAN-JOIN hint + query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + "), NO_INDEX_SERVER_MERGE */ t_id, k1, k2, k3, V1 from " + dataTableFullName + " where v1<='z' and k3 > 1 order by V1,t_id"; + rs = conn1.createStatement().executeQuery("EXPLAIN " + query); + expected = + "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dataTableName + "\n" + + " SERVER FILTER BY K3 > 1\n" + + " SERVER SORTED BY \\[" + dataTableName + "\\.V1, " + dataTableName + "\\.T_ID\\]\n" + + "CLIENT MERGE SORT\n" + + " SKIP-SCAN-JOIN TABLE 0\n" + + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName + " \\[\\*\\] - \\['z'\\]\n" + + " SERVER FILTER BY FIRST KEY ONLY\n" + + " DYNAMIC SERVER FILTER BY \\(\"" + dataTableName + "\\.T_ID\", \"" + dataTableName + "\\.K1\", \"" + dataTableName + "\\.K2\"\\) IN \\(\\(\\$\\d+\\.\\$\\d+, \\$\\d+\\.\\$\\d+, \\$\\d+\\.\\$\\d+\\)\\)"; + actual = QueryUtil.getExplainPlan(rs); + assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, Pattern.matches(expected, actual)); + + + rs = conn1.createStatement().executeQuery(query); + assertTrue(rs.next()); + assertEquals("f", rs.getString("t_id")); + assertEquals(1, rs.getInt("k1")); + assertEquals(2, rs.getInt("k2")); + assertEquals(3, rs.getInt("k3")); + assertEquals("a", rs.getString("V1")); + assertTrue(rs.next()); + assertEquals("j", rs.getString("t_id")); + assertEquals(2, rs.getInt("k1")); + assertEquals(4, rs.getInt("k2")); + assertEquals(2, rs.getInt("k3")); + assertEquals("a", rs.getString("V1")); + assertTrue(rs.next()); + assertEquals("b", rs.getString("t_id")); + assertEquals(1, rs.getInt("k1")); + assertEquals(2, rs.getInt("k2")); + assertEquals(4, rs.getInt("k3")); + assertEquals("z", rs.getString("V1")); + assertFalse(rs.next()); + query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ t_id, V1, k3 from " + dataTableFullName + " where v1 <='z' group by v1,t_id, k3"; rs = conn1.createStatement().executeQuery("EXPLAIN " + query); expected = 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 c0a516e0534..63bc36e2a50 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 @@ -339,9 +339,16 @@ private QueryPlan addPlan(PhoenixStatement statement, SelectStatement select, PT if (indexState == PIndexState.ACTIVE || indexState == PIndexState.PENDING_ACTIVE || (indexState == PIndexState.PENDING_DISABLE && isUnderPendingDisableThreshold(indexTableRef.getCurrentTime(), indexTable.getIndexDisableTimestamp()))) { try { + if (select.getHint().hasHint(HintNode.Hint.NO_INDEX_SERVER_MERGE)) { + String schemaNameStr = index.getSchemaName() == null ? null + : index.getSchemaName().getString(); + String tableNameStr = index.getTableName() == null ? null + : index.getTableName().getString(); + throw new ColumnNotFoundException(schemaNameStr, tableNameStr, null, "*"); + } // translate nodes that match expressions that are indexed to the associated column parse node - indexSelect = ParseNodeRewriter.rewrite(indexSelect, new IndexExpressionParseNodeRewriter(index, null, statement.getConnection(), indexSelect.getUdfParseNodes())); - QueryCompiler compiler = new QueryCompiler(statement, indexSelect, resolver, targetColumns, parallelIteratorFactory, dataPlan.getContext().getSequenceManager(), isProjected, true, dataPlans); + SelectStatement rewrittenIndexSelect = ParseNodeRewriter.rewrite(indexSelect, new IndexExpressionParseNodeRewriter(index, null, statement.getConnection(), indexSelect.getUdfParseNodes())); + QueryCompiler compiler = new QueryCompiler(statement, rewrittenIndexSelect, resolver, targetColumns, parallelIteratorFactory, dataPlan.getContext().getSequenceManager(), isProjected, true, dataPlans); QueryPlan plan = compiler.compile(); if (indexTable.getIndexType() == IndexType.UNCOVERED_GLOBAL) { @@ -377,10 +384,10 @@ && isUnderPendingDisableThreshold(indexTableRef.getCurrentTime(), if (plan.getProjector().getColumnCount() == nColumns) { return plan; } else { - String schemaNameStr = index.getSchemaName()==null? null + String schemaNameStr = index.getSchemaName() == null ? null : index.getSchemaName().getString(); - String tableNameStr = index.getTableName()==null? null - :index.getTableName().getString(); + String tableNameStr = index.getTableName() == null ? null + : index.getTableName().getString(); throw new ColumnNotFoundException(schemaNameStr, tableNameStr, null, "*"); } } @@ -391,6 +398,10 @@ && isUnderPendingDisableThreshold(indexTableRef.getCurrentTime(), * otherwise we just don't use this index (as opposed to trying to join back from * the index table to the data table. */ + // Reset the state changes from the attempt above + indexTableRef.setHinted(false); + dataPlan.getContext().setUncoveredIndex(false); + SelectStatement dataSelect = (SelectStatement)dataPlan.getStatement(); ParseNode where = dataSelect.getWhere(); if (isHinted && where != null) { diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/HintNode.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/HintNode.java index 60334ac3d3b..baba60bc917 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/HintNode.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/HintNode.java @@ -118,6 +118,10 @@ public enum Hint { * Issue https://issues.apache.org/jira/browse/PHOENIX-4751. */ HASH_AGGREGATE, + /** + * Do not use server merge for hinted uncovered indexes + */ + NO_INDEX_SERVER_MERGE }; private final Map hints; 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 886f0ddac58..a0c40ce32d6 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 @@ -7008,4 +7008,39 @@ public void testUncoveredPhoenix6969() throws Exception { } } + @Test + public void testUncoveredPhoenix6984() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl()); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE D (\n" + "K1 CHAR(6) NOT NULL,\n" + + "K2 VARCHAR(22) NOT NULL,\n" + + "K3 CHAR(2) NOT NULL,\n" + + "K4 VARCHAR(36) NOT NULL,\n" + + "V1 TIMESTAMP,\n" + + "V2 TIMESTAMP,\n" + + "CONSTRAINT PK_BILLING_ORDER PRIMARY KEY (K1,K2,K3,K4))"); + + stmt.execute("CREATE INDEX I ON D(K2, K1, K3, K4)"); + String query = + "SELECT /*+ INDEX(D I), NO_INDEX_SERVER_MERGE */ * " + + "FROM D " + + "WHERE K2 = 'XXX' AND " + + "V2 >= TIMESTAMP '2023-05-31 23:59:59.000' AND " + + "V1 <= TIMESTAMP '2023-04-01 00:00:00.000' " + + "ORDER BY V2 asc"; + ResultSet rs = stmt.executeQuery("EXPLAIN " + query); + String explainPlan = QueryUtil.getExplainPlan(rs); + assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER D\n" + + " SERVER FILTER BY (V2 >= TIMESTAMP '2023-05-31 23:59:59.000'" + + " AND V1 <= TIMESTAMP '2023-04-01 00:00:00.000')\n" + + " SERVER SORTED BY [D.V2]\n" + + "CLIENT MERGE SORT\n" + + " SKIP-SCAN-JOIN TABLE 0\n" + + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER I ['XXX']\n" + + " SERVER FILTER BY FIRST KEY ONLY\n" + + " DYNAMIC SERVER FILTER BY (\"D.K1\", \"D.K2\", \"D.K3\", \"D.K4\")" + + " IN (($2.$4, $2.$5, $2.$6, $2.$7))", + explainPlan); + } + } }