docs: private Map<Pair<String, Integer>, Pair<String, Integer>> getViewColumnMaps(MaterializedViewInfo mv) {
          // A functor to iterate table columns from given MV, the first k entries are k GBY columns/expressions.
          final Iterable<Column> ciViewColumns = () -> mv.getDest().getColumns().iterator();
          // NOTE: to establish mapping between GBY expressions, we have to maintain some order on either (but not both)
          // of MV GBY expression, or SELECT stmt GBY expression.
          final List<AbstractExpression> mvGbys =          // Ordered collection of MV's group-by expressions (ordered by MV's creation stmt)
                  m_selectStmt.hasComplexGroupby() ? getGbyExpressions(mv) : new ArrayList<>(); //  when it contains complex GBY expression.
          // Assertion that both MV and SELECT stmt have same group-by expressions. This is guaranteed by caller in gbyMatchers()
          // method, which is guaranteed by calling gbyTablesEqual() method.
          assert(new HashSet<>(mvGbys).equals(m_selectStmt.hasComplexGroupby() ?
                  m_selectStmt.getGroupByColumns().stream()
                          .map(ci -> transformExpressionRidofPVE(ci.m_expression).anonymize())
                          .collect(Collectors.toSet()) : new HashSet<>()));              // warranted by precondition (gbyMatcher() caller).
          final Map<Integer, Integer> selGbyColIndexMap = // SELECT stmt GBY column index ==> VIEW table column index
                  m_selectStmt.displayColumns().stream().flatMap(ci -> {
                      // find index of a SELECT's GBY expression into MV's GBY expression
                      final int index = mvGbys.indexOf(transformExpressionRidofPVE(ci.m_expression).anonymize());
                      return index >= 0 ?     // mark index only for GBY expressions.
                              Stream.of(new AbstractMap.SimpleEntry<>(ci.m_index, index)) : Stream.empty();
                  }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
          // <aggType, viewSrcTblColIndx> ==> <viewTblColName, viewTblColIndx>
          final Map<Pair<Integer, Integer>, Pair<String, Integer>> nonGbyFromView =
                  StreamSupport.stream(ciViewColumns.spliterator(), false)    // iterate over MV table columns,
                          .skip(mvGbys.size())        // where the first k columns correspond to k GBY columns/expressions,
                          .collect(Collectors.toMap(col -> {
                                      final Column matCol = col.getMatviewsource();   // source column of MV column/aggregation
                                      return Pair.of(col.getAggregatetype(),
                                              matCol == null ? -1 : matCol.getIndex());    // -1 when it's arithmetic expression like "C1 - C2"
                                  },
                                  col -> Pair.of(col.getTypeName(), col.getIndex()),
                                  (a, b) -> a));      // dedup when MV contains duplicated entries
          // <aggType, viewSrcTblColIndx> ==> <displayColName, displayColIndx>
          final AtomicBoolean hasAggregateOnGbyCol = new AtomicBoolean(false);        // when SELECT contains aggregate on its GBY column, and view doesn't
          final Map<Pair<Integer, Integer>, Pair<String, Integer>> nonGbyFromStmt =
                  m_selectStmt.displayColumns().stream()                              // iterate over SELECT stmt's display columns and
                          .filter(ci -> !selGbyColIndexMap.containsKey(ci.m_index))   // collect all but GBY columns/expressions,
                          .flatMap(ci -> {
                              final Pair<String, Integer> value = Pair.of(ci.m_columnName, ci.m_index);  // Mapped value: display column name and index
                              if (ci.m_expression instanceof AggregateExpression) {   // if GBY over an aggregation function, then
                                  final AbstractExpression left = ci.m_expression.getLeft();
                                  return Stream.of(Pair.of(Pair.of(ci.m_expression.getExpressionType().getValue(), // extract aggregation type and argument:
                                          // aggregate on what? For simple column, it contains column index; for "count(*)", set to -1;
                                          left == null ? -1 : ((TupleValueExpression) left).getColumnIndex()), value));    // use it to map into value type.
                              } else if (ci.m_expression instanceof TupleValueExpression) {    // otherwise, when it's either column or distinct column: aggregation type is set to VALUE_TUPLE.
                                  return Stream.of(Pair.of(Pair.of(ExpressionType.VALUE_TUPLE.getValue(),
                                          ((TupleValueExpression) ci.m_expression).getColumnIndex()), value));
                              } else {        // it could also be an aggregate function on a group-by column: do not support for now.
                                  hasAggregateOnGbyCol.set(true);
                                  return Stream.empty();
                              }
                          }).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
          if (! hasAggregateOnGbyCol.get() &&
                  nonGbyFromView.keySet().containsAll(nonGbyFromStmt.keySet())) {   // when {SELECT stmt non-GBY columns} \belongs {VIEW columns non-GBY columns}
              // Join the above 2 maps to get <display column name, display column index> ==> <MV column name, MV column index> for non-GBY display columns in SELECT stmt
              final Map<Pair<String, Integer>, Pair<String, Integer>> rel = nonGbyFromStmt.entrySet().stream()
                      .collect(Collectors.toMap(Map.Entry::getValue, kv -> nonGbyFromView.get(kv.getKey())));
              // Now that we know that the candidate MV can be used to answer the SELECT query, check GBY relations from MV
              final List<String> gbyNames = StreamSupport.stream(ciViewColumns.spliterator(), false)
                      .limit(mvGbys.size()).map(Column::getTypeName).collect(Collectors.toList());
              rel.putAll(m_selectStmt.m_displayColumns.stream()                 // Augment the map with GBY columns:
                      .filter(ci -> selGbyColIndexMap.containsKey(ci.m_index))  // starting from SELECT stmt's non-GBY display columns,
                      .map(ci -> {                                              // and index into MV's GBY columns to complete the relations.
                          final Integer viewColIndx = selGbyColIndexMap.get(ci.m_index);
                          return Pair.of(Pair.of(ci.m_columnName, ci.m_index),
                                  Pair.of(gbyNames.get(viewColIndx), viewColIndx));
                      }).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)));
              return rel;
          } else {
              return null;
          }
      }