docs: private boolean switchToIndexScanForGroupBy(AbstractPlanNode candidate,
              IndexGroupByInfo gbInfo) {
          if (! m_parsedSelect.isGrouped()) {
              return false;
          }
  
          if (candidate instanceof IndexScanPlanNode) {
              calculateIndexGroupByInfo((IndexScanPlanNode) candidate, gbInfo);
              if (gbInfo.m_coveredGroupByColumns != null &&
                      !gbInfo.m_coveredGroupByColumns.isEmpty()) {
                  // The candidate index does cover all or some
                  // of the GROUP BY columns and can be serialized
                  gbInfo.m_indexAccess = candidate;
                  return true;
              }
              return false;
          }
  
          AbstractPlanNode sourceSeqScan = findSeqScanCandidateForGroupBy(candidate);
          if (sourceSeqScan == null) {
              return false;
          }
          assert(sourceSeqScan instanceof SeqScanPlanNode);
  
          AbstractPlanNode parent = null;
          if (sourceSeqScan.getParentCount() > 0) {
              parent = sourceSeqScan.getParent(0);
          }
          AbstractPlanNode indexAccess = indexAccessForGroupByExprs(
                  (SeqScanPlanNode)sourceSeqScan, gbInfo);
  
          if (indexAccess.getPlanNodeType() != PlanNodeType.INDEXSCAN) {
              // does not find proper index to replace sequential scan
              return false;
          }
  
          gbInfo.m_indexAccess = indexAccess;
          if (parent != null) {
              // have a parent and would like to replace
              // the sequential scan with an index scan
              indexAccess.clearParents();
              // For two children join node, index 0 is its outer side
              parent.replaceChild(0, indexAccess);
  
              return false;
          }
  
          // parent is null and switched to index scan from sequential scan
          return true;
      }