Skip to content

Commit 9ee4c90

Browse files
author
Saurabh Rai
committed
PHOENIX-6644 Fix ResultSet.getString(columnName) for view index queries with constant columns
1 parent f90725b commit 9ee4c90

File tree

4 files changed

+374
-2
lines changed

4 files changed

+374
-2
lines changed

phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryCompiler.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,15 @@ protected QueryPlan compileSingleFlatQuery(StatementContext context, SelectState
841841
.getBoolean(WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB, DEFAULT_WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB);
842842
RowProjector projector = ProjectionCompiler.compile(context, select, groupBy,
843843
asSubquery ? Collections.emptyList() : targetColumns, where, wildcardIncludesDynamicCols);
844+
845+
// PHOENIX-6644: Merge column name mappings from the original data plan if this is an
846+
// index query. This preserves original column names for ResultSet.getString(columnName)
847+
// when view constants or other optimizations rewrite column references.
848+
QueryPlan dataPlanForMerge = dataPlans.get(tableRef);
849+
if (dataPlanForMerge != null && dataPlanForMerge.getProjector() != null) {
850+
projector = projector.mergeColumnNameMappings(dataPlanForMerge.getProjector());
851+
}
852+
844853
OrderBy orderBy = OrderByCompiler.compile(context, select, groupBy, limit, compiledOffset,
845854
projector, innerPlan, where);
846855
context.getAggregationManager().compile(context, groupBy);

phoenix-core-client/src/main/java/org/apache/phoenix/compile/RowProjector.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ public RowProjector(List<? extends ColumnProjector> columnProjectors, int estima
7676
public RowProjector(List<? extends ColumnProjector> columnProjectors, int estimatedRowSize,
7777
boolean isProjectEmptyKeyValue, boolean hasUDFs, boolean isProjectAll,
7878
boolean isProjectDynColsInWildcardQueries) {
79+
this(columnProjectors, estimatedRowSize, isProjectEmptyKeyValue, hasUDFs, isProjectAll,
80+
isProjectDynColsInWildcardQueries, null);
81+
}
82+
83+
/**
84+
* Construct RowProjector based on a list of ColumnProjectors with additional name mappings.
85+
* @param columnProjectors ordered list of ColumnProjectors corresponding to projected columns in
86+
* SELECT clause aggregating coprocessor. Only required in the case of an
87+
* aggregate query with a limit clause and otherwise may be null.
88+
* @param additionalNameMappings additional column name to position mappings to merge into the
89+
* reverseIndex during construction. Used for preserving original
90+
* column names when query optimization rewrites them (e.g., view
91+
* constants). May be null.
92+
*/
93+
public RowProjector(List<? extends ColumnProjector> columnProjectors, int estimatedRowSize,
94+
boolean isProjectEmptyKeyValue, boolean hasUDFs, boolean isProjectAll,
95+
boolean isProjectDynColsInWildcardQueries, ListMultimap<String, Integer> additionalNameMappings) {
7996
this.columnProjectors = Collections.unmodifiableList(columnProjectors);
8097
int position = columnProjectors.size();
8198
reverseIndex = ArrayListMultimap.<String, Integer> create();
@@ -91,6 +108,16 @@ public RowProjector(List<? extends ColumnProjector> columnProjectors, int estima
91108
SchemaUtil.getColumnName(colProjector.getTableName(), colProjector.getLabel()), position);
92109
}
93110
}
111+
112+
// Merge additional name mappings if provided (for PHOENIX-6644)
113+
if (additionalNameMappings != null) {
114+
for (String name : additionalNameMappings.keySet()) {
115+
if (!reverseIndex.containsKey(name)) {
116+
reverseIndex.putAll(name, additionalNameMappings.get(name));
117+
}
118+
}
119+
}
120+
94121
this.allCaseSensitive = allCaseSensitive;
95122
this.someCaseSensitive = someCaseSensitive;
96123
this.estimatedSize = estimatedRowSize;
@@ -206,4 +233,56 @@ public void reset() {
206233
projector.getExpression().reset();
207234
}
208235
}
236+
237+
/**
238+
* PHOENIX-6644: Creates a new RowProjector with additional column name mappings merged from another projector.
239+
* This is useful when an optimized query (e.g., using an index) rewrites column references but we
240+
* want to preserve the original column names for ResultSet.getString(columnName) compatibility.
241+
*
242+
* For example, when a view has "WHERE v1 = 'a'" and an index is used, the optimizer may rewrite
243+
* "SELECT v1 FROM view" to "SELECT 'a' FROM index". This method adds the original column name "v1"
244+
* to the reverseIndex so ResultSet.getString("v1") continues to work.
245+
*
246+
* @param sourceProjector the projector containing original column name mappings to preserve
247+
* @return a new RowProjector with merged column name mappings
248+
*/
249+
public RowProjector mergeColumnNameMappings(RowProjector sourceProjector) {
250+
if (this.columnProjectors.size() != sourceProjector.columnProjectors.size()) {
251+
return this;
252+
}
253+
254+
ListMultimap<String, Integer> additionalMappings = ArrayListMultimap.create();
255+
256+
for (int i = 0; i < sourceProjector.columnProjectors.size(); i++) {
257+
ColumnProjector sourceCol = sourceProjector.columnProjectors.get(i);
258+
ColumnProjector currentCol = this.columnProjectors.get(i);
259+
260+
// Only add source labels if they're different from current labels
261+
// This preserves original names like "v1" when optimizer rewrites to "'a'"
262+
String sourceLabel = sourceCol.getLabel();
263+
String currentLabel = currentCol.getLabel();
264+
265+
if (!sourceLabel.equals(currentLabel)) {
266+
additionalMappings.put(sourceLabel, i);
267+
}
268+
269+
// Also add qualified name from source if different
270+
if (!sourceCol.getTableName().isEmpty()) {
271+
String sourceQualifiedName =
272+
SchemaUtil.getColumnName(sourceCol.getTableName(), sourceCol.getLabel());
273+
String currentQualifiedName = currentCol.getTableName().isEmpty() ? "" :
274+
SchemaUtil.getColumnName(currentCol.getTableName(), currentCol.getLabel());
275+
276+
if (!sourceQualifiedName.equals(currentQualifiedName)) {
277+
additionalMappings.put(sourceQualifiedName, i);
278+
}
279+
}
280+
}
281+
282+
// Create new RowProjector with additional mappings merged during construction
283+
// This maintains immutability.
284+
return new RowProjector(this.columnProjectors, this.estimatedSize,
285+
this.isProjectEmptyKeyValue, this.hasUDFs, this.isProjectAll,
286+
this.isProjectDynColsInWildcardQueries, additionalMappings);
287+
}
209288
}

phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,10 @@ public void testGlobalIndexOptimizationOnSharedIndex() throws Exception {
426426
assertEquals(2, rs.getInt("k1"));
427427
assertEquals(4, rs.getInt("k2"));
428428
assertEquals(2, rs.getInt("k3"));
429-
assertEquals("a", rs.getString(5)); // TODO use name v1 instead of position 5, see
430-
// PHOENIX-6644
429+
assertEquals("a", rs.getString(5));
430+
// Fixed in PHOENIX-6644 - rs.getString("v1") now works
431+
// See ViewIndexColumnNameGetterIT for comprehensive tests
432+
assertEquals("a", rs.getString("v1"));
431433
assertFalse(rs.next());
432434
} finally {
433435
conn1.close();

0 commit comments

Comments
 (0)