@@ -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}
0 commit comments