From da47e431a69a9df598d22189efe2cacad7bd14eb Mon Sep 17 00:00:00 2001 From: xavifeds8 Date: Mon, 18 May 2026 23:44:02 +0530 Subject: [PATCH] PHOENIX-5236 Multiple dynamic columns in WHERE clause is not working --- .../apache/phoenix/compile/WhereCompiler.java | 1 + .../phoenix/end2end/DynamicColumnIT.java | 134 ++++++++++++++++++ .../phoenix/compile/WhereCompilerTest.java | 129 +++++++++++++++++ 3 files changed, 264 insertions(+) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereCompiler.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereCompiler.java index 6dee0c2f514..0641f228718 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereCompiler.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereCompiler.java @@ -595,6 +595,7 @@ public Void visit(KeyValueColumnExpression expression) { break; case MULTIPLE: filter = isPossibleToUseEncodedCQFilter(encodingScheme, storageScheme) + && !ScanUtil.hasDynamicColumns(table) ? new MultiEncodedCQKeyValueComparisonFilter(whereClause, encodingScheme, allCFs, essentialCF) : (disambiguateWithFamily diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DynamicColumnIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DynamicColumnIT.java index dff5c907bd4..8a8363c1a64 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DynamicColumnIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DynamicColumnIT.java @@ -273,4 +273,138 @@ public void testDynamicColumnOnNewTable() throws Exception { } } + /** + * Test that multiple dynamic columns in a WHERE clause work correctly on a table + * with column encoding enabled (the default). Regression test for PHOENIX-5236. + */ + @Test + public void testMultipleDynamicColumnsInWhere() throws Exception { + String tableName = generateUniqueName(); + String ddl = "create table " + tableName + + " (id VARCHAR PRIMARY KEY, name VARCHAR)"; + try (Connection conn = DriverManager.getConnection(getUrl())) { + conn.createStatement().execute(ddl); + + // Upsert rows with dynamic columns + String dml = "UPSERT INTO " + tableName + + "(id, name, city VARCHAR, department VARCHAR, age INTEGER)" + + " VALUES (?, ?, ?, ?, ?)"; + try (PreparedStatement stmt = conn.prepareStatement(dml)) { + stmt.setString(1, "1"); + stmt.setString(2, "John"); + stmt.setString(3, "Chennai"); + stmt.setString(4, "Engineering"); + stmt.setInt(5, 30); + stmt.executeUpdate(); + + stmt.setString(1, "2"); + stmt.setString(2, "Jane"); + stmt.setString(3, "Mumbai"); + stmt.setString(4, "Marketing"); + stmt.setInt(5, 28); + stmt.executeUpdate(); + + stmt.setString(1, "3"); + stmt.setString(2, "Bob"); + stmt.setString(3, "Chennai"); + stmt.setString(4, "Marketing"); + stmt.setInt(5, 35); + stmt.executeUpdate(); + + conn.commit(); + } + + // Single dynamic column in WHERE - baseline + String query = "SELECT id, name FROM " + tableName + + " (city VARCHAR) WHERE city = ?"; + try (PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, "Chennai"); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("John", rs.getString(2)); + assertTrue(rs.next()); + assertEquals("3", rs.getString(1)); + assertEquals("Bob", rs.getString(2)); + assertFalse(rs.next()); + } + + // Multiple dynamic columns in WHERE - PHOENIX-5236 bug + query = "SELECT id, name FROM " + tableName + + " (city VARCHAR, department VARCHAR, age INTEGER)" + + " WHERE city = ? AND department = ?"; + try (PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, "Chennai"); + stmt.setString(2, "Engineering"); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("John", rs.getString(2)); + assertFalse(rs.next()); + } + + // Dynamic columns compared to each other + query = "SELECT id, name FROM " + tableName + + " (city VARCHAR, department VARCHAR)" + + " WHERE city = department"; + try (PreparedStatement stmt = conn.prepareStatement(query)) { + ResultSet rs = stmt.executeQuery(); + assertFalse(rs.next()); + } + + // 3 dynamic columns in WHERE + query = "SELECT id, name FROM " + tableName + + " (city VARCHAR, department VARCHAR, age INTEGER)" + + " WHERE city = ? AND department = ? AND age > ?"; + try (PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, "Chennai"); + stmt.setString(2, "Engineering"); + stmt.setInt(3, 25); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("John", rs.getString(2)); + assertFalse(rs.next()); + } + + // 1 schema column + 2 dynamic columns in WHERE (mixed) + query = "SELECT id FROM " + tableName + + " (city VARCHAR, department VARCHAR)" + + " WHERE name = ? AND city = ? AND department = ?"; + try (PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, "John"); + stmt.setString(2, "Chennai"); + stmt.setString(3, "Engineering"); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertFalse(rs.next()); + } + + // 2 dynamic + 1 schema column in WHERE (mixed, different order) + query = "SELECT id FROM " + tableName + + " (city VARCHAR, age INTEGER)" + + " WHERE city = ? AND age > ? AND name = ?"; + try (PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, "Chennai"); + stmt.setInt(2, 30); + stmt.setString(3, "Bob"); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("3", rs.getString(1)); + assertFalse(rs.next()); + } + + // No dynamic columns in WHERE (schema-only, baseline - no regression) + query = "SELECT id FROM " + tableName + " WHERE name = ?"; + try (PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, "Jane"); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("2", rs.getString(1)); + assertFalse(rs.next()); + } + } + } + } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereCompilerTest.java index 17ab5fcc3cf..da6f7284815 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereCompilerTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereCompilerTest.java @@ -59,7 +59,10 @@ import org.apache.phoenix.expression.LiteralExpression; import org.apache.phoenix.expression.RowKeyColumnExpression; import org.apache.phoenix.expression.function.SubstrFunction; +import org.apache.phoenix.filter.MultiCQKeyValueComparisonFilter; +import org.apache.phoenix.filter.MultiEncodedCQKeyValueComparisonFilter; import org.apache.phoenix.filter.RowKeyComparisonFilter; +import org.apache.phoenix.filter.SingleCQKeyValueComparisonFilter; import org.apache.phoenix.filter.SkipScanFilter; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; @@ -1181,4 +1184,130 @@ public void testWhereInclusion() throws SQLException { } } + // ===== PHOENIX-5236: Filter selection tests for dynamic columns ===== + + /** + * Test that a single dynamic column in WHERE uses SingleCQKeyValueComparisonFilter. + */ + @Test + public void testSingleDynamicColumnFilterSelection() throws SQLException { + PhoenixConnection pconn = + DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES)) + .unwrap(PhoenixConnection.class); + String tableName = "T_" + generateUniqueName(); + pconn.createStatement().execute( + "CREATE TABLE " + tableName + " (id VARCHAR PRIMARY KEY, name VARCHAR)"); + + String query = "SELECT id FROM " + tableName + + " (city VARCHAR) WHERE city = 'Chennai'"; + PhoenixPreparedStatement pstmt = new PhoenixPreparedStatement(pconn, query); + QueryPlan plan = pstmt.optimizeQuery(); + Scan scan = plan.getContext().getScan(); + Filter filter = scan.getFilter(); + assertTrue("Expected SingleCQKeyValueComparisonFilter for single dynamic column in WHERE, got: " + + (filter == null ? "null" : filter.getClass().getSimpleName()), + filter instanceof SingleCQKeyValueComparisonFilter); + } + + /** + * Test that multiple dynamic columns in WHERE uses MultiCQKeyValueComparisonFilter + * (not MultiEncodedCQKeyValueComparisonFilter) on a table with encoding enabled. + * This is the regression test for PHOENIX-5236. + */ + @Test + public void testMultipleDynamicColumnsFilterSelection() throws SQLException { + PhoenixConnection pconn = + DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES)) + .unwrap(PhoenixConnection.class); + String tableName = "T_" + generateUniqueName(); + pconn.createStatement().execute( + "CREATE TABLE " + tableName + " (id VARCHAR PRIMARY KEY, name VARCHAR)"); + + String query = "SELECT id FROM " + tableName + + " (city VARCHAR, department VARCHAR) WHERE city = 'Chennai' AND department = 'Engineering'"; + PhoenixPreparedStatement pstmt = new PhoenixPreparedStatement(pconn, query); + QueryPlan plan = pstmt.optimizeQuery(); + Scan scan = plan.getContext().getScan(); + Filter filter = scan.getFilter(); + assertTrue( + "Expected MultiCQKeyValueComparisonFilter for multiple dynamic columns in WHERE, got: " + + (filter == null ? "null" : filter.getClass().getSimpleName()), + filter instanceof MultiCQKeyValueComparisonFilter); + } + + /** + * Test that multiple schema-defined columns in WHERE still uses + * MultiEncodedCQKeyValueComparisonFilter on a table with encoding enabled. + */ + @Test + public void testMultipleSchemaColumnsStillUsesEncodedFilter() throws SQLException { + PhoenixConnection pconn = + DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES)) + .unwrap(PhoenixConnection.class); + String tableName = "T_" + generateUniqueName(); + pconn.createStatement().execute( + "CREATE TABLE " + tableName + + " (id VARCHAR PRIMARY KEY, city VARCHAR, department VARCHAR)"); + + String query = "SELECT id FROM " + tableName + + " WHERE city = 'Chennai' AND department = 'Engineering'"; + PhoenixPreparedStatement pstmt = newPreparedStatement(pconn, query); + QueryPlan plan = pstmt.optimizeQuery(); + Scan scan = plan.getContext().getScan(); + Filter filter = scan.getFilter(); + assertTrue( + "Expected MultiEncodedCQKeyValueComparisonFilter for schema columns in WHERE, got: " + + (filter == null ? "null" : filter.getClass().getSimpleName()), + filter instanceof MultiEncodedCQKeyValueComparisonFilter); + } + + /** + * Test that a table with COLUMN_ENCODED_BYTES=NONE uses MultiCQKeyValueComparisonFilter + * for multiple columns (regardless of whether they are dynamic). + */ + @Test + public void testNonEncodedTableUsesMultiCQFilter() throws SQLException { + PhoenixConnection pconn = + DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES)) + .unwrap(PhoenixConnection.class); + String tableName = "T_" + generateUniqueName(); + pconn.createStatement().execute( + "CREATE TABLE " + tableName + + " (id VARCHAR PRIMARY KEY, city VARCHAR, department VARCHAR) COLUMN_ENCODED_BYTES=NONE"); + + String query = "SELECT id FROM " + tableName + + " WHERE city = 'Chennai' AND department = 'Engineering'"; + PhoenixPreparedStatement pstmt = newPreparedStatement(pconn, query); + QueryPlan plan = pstmt.optimizeQuery(); + Scan scan = plan.getContext().getScan(); + Filter filter = scan.getFilter(); + assertTrue( + "Expected MultiCQKeyValueComparisonFilter for non-encoded table, got: " + + (filter == null ? "null" : filter.getClass().getSimpleName()), + filter instanceof MultiCQKeyValueComparisonFilter); + } + + /** + * Test that a single schema column in WHERE uses SingleCQKeyValueComparisonFilter + * (baseline behavior unchanged). + */ + @Test + public void testSingleSchemaColumnFilterSelection() throws SQLException { + PhoenixConnection pconn = + DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES)) + .unwrap(PhoenixConnection.class); + String tableName = "T_" + generateUniqueName(); + pconn.createStatement().execute( + "CREATE TABLE " + tableName + " (id VARCHAR PRIMARY KEY, city VARCHAR)"); + + String query = "SELECT id FROM " + tableName + " WHERE city = 'Chennai'"; + PhoenixPreparedStatement pstmt = newPreparedStatement(pconn, query); + QueryPlan plan = pstmt.optimizeQuery(); + Scan scan = plan.getContext().getScan(); + Filter filter = scan.getFilter(); + assertTrue("Expected SingleCQKeyValueComparisonFilter for single schema column in WHERE, got: " + + (filter == null ? "null" : filter.getClass().getSimpleName()), + filter instanceof SingleCQKeyValueComparisonFilter); + } + }